/*

TODO
====
* either wrap helper functions into anonymous namespace or declare them as static
* add functions for generating geomorphing-related buffers (vertex_level,offset_table,..border_mask?)

*/  

#include <cassert>

bool is_pow2(int x)
{
  return (x && !(x & (x - 1)));
}

int floor_log2(int x)
{
  int r = 0;
  
  while(x>>=1)
  {
    r++;  
  }
  
  return r;
}

bool is_valid_chunksize(int chunksize)
{
  return (chunksize>0 && (chunksize==1 || is_pow2(chunksize-1)));
}

int patch_mask(int x,int y,int num_patches,int stitchmask)
{
  int patchmask = 0;

  if ((x==0            ) && (stitchmask & 1)) patchmask |= 1;
  if ((x==num_patches-1) && (stitchmask & 2)) patchmask |= 2;
  if ((y==0            ) && (stitchmask & 4)) patchmask |= 4;
  if ((y==num_patches-1) && (stitchmask & 8)) patchmask |= 8;  
  
  return patchmask;
}

int patch_num_tris(int patchmask)
{
  int num_tris = 0;

  num_tris += (patchmask & 1) ? 1 : 2;
  num_tris += (patchmask & 2) ? 1 : 2;
  num_tris += (patchmask & 4) ? 1 : 2;
  num_tris += (patchmask & 8) ? 1 : 2;

  return num_tris;
}

void patch_gen_ids(int patchmask,int offset,int pitch,int width,int* buffer)
{
  // 4 sides, 2 tris per side, 3 verts per tri, 2 coords per vert
  const int table_fine[4][2][3][2] = { { {{0,0},{0,1},{1,1}}, {{0,1},{0,2},{1,1}} },
                                       { {{2,1},{2,0},{1,1}}, {{2,2},{2,1},{1,1}} },
                                       { {{1,0},{0,0},{1,1}}, {{2,0},{1,0},{1,1}} },
                                       { {{0,2},{1,2},{1,1}}, {{1,2},{2,2},{1,1}} } };

  // 4 sides, 1 tri per side, 3 verts per tri, 2 coords per vert
  const int table_coarse[4][3][2] = { {{0,0},{0,2},{1,1}},
                                      {{2,2},{2,0},{1,1}},
                                      {{2,0},{0,0},{1,1}},
                                      {{0,2},{2,2},{1,1}} };
                                                      
  for(int side=0;side<4;side++)
  {
    if (patchmask & (1 << side))
    {
      for(int vtx=0;vtx<3;vtx++)
      {
        *buffer++ = offset + table_coarse[side][vtx][0]*pitch + 
                             table_coarse[side][vtx][1]*pitch*width;
      }
    }
    else
    {
      for(int tri=0;tri<2;tri++)
      for(int vtx=0;vtx<3;vtx++)
      {
        *buffer++ = offset + table_fine[side][tri][vtx][0]*pitch +
                             table_fine[side][tri][vtx][1]*pitch*width;
      }
    }
  }
}

int geomipmap_num_lods(int chunksize)
{
  assert(is_valid_chunksize(chunksize));
  
  return chunksize > 1 ? floor_log2(chunksize-1)+1 : 1;  
}

int geomipmap_stitch_mask(bool left,bool right,bool top,bool bottom)
{
  int stitchmask = 0;
  
  if (left)   stitchmask |= 1;
  if (right)  stitchmask |= 2;
  if (top)    stitchmask |= 4;
  if (bottom) stitchmask |= 8;
  
  return stitchmask;
}

int geomipmap_num_tris(int chunksize,int lod,int stitchmask)
{
  assert(is_valid_chunksize(chunksize));
  assert(lod < geomipmap_num_lods(chunksize));
  assert(stitchmask>=0 && stitchmask<=15);

  int num_tris = 0;
  
  if (lod==geomipmap_num_lods(chunksize)-1)
  {
    num_tris = 2;
  }
  else
  {
    const int pitch = 1 << lod;
    const int num_patches = (chunksize-1) / (2*pitch);
    
    for(int y=0;y<num_patches;y++)   
    for(int x=0;x<num_patches;x++)
    {
      num_tris += patch_num_tris(patch_mask(x,y,num_patches,stitchmask));      
    }
  }  
    
  return num_tris;
}

int geomipmap_num_ids(int chunksize,int lod,int stitchmask)
{
  assert(is_valid_chunksize(chunksize));
  assert(lod < geomipmap_num_lods(chunksize));
  assert(stitchmask>=0 && stitchmask<=15);
  
  return 3*geomipmap_num_tris(chunksize,lod,stitchmask);
}

void geomipmap_gen_ids(int chunksize,int lod,int stitchmask,int width,int* buffer)
{
  assert(is_valid_chunksize(chunksize));
  assert(lod < geomipmap_num_lods(chunksize));
  assert(stitchmask>=0 && stitchmask<=15);
  
  if (lod==geomipmap_num_lods(chunksize)-1)
  {
    *buffer++ = 0;
    *buffer++ = (chunksize-1)+(chunksize-1)*width;
    *buffer++ = (chunksize-1);

    *buffer++ = 0;
    *buffer++ = (chunksize-1)*width;
    *buffer++ = (chunksize-1)+(chunksize-1)*width;
  }
  else
  {
    const int pitch = 1 << lod;
    const int num_patches = (chunksize-1) / (2*pitch);
    
    for(int y=0;y<num_patches;y++)   
    for(int x=0;x<num_patches;x++)
    {
      const int offset = x*2*pitch + y*2*pitch*width;      
      const int patchmask = patch_mask(x,y,num_patches,stitchmask);
      
      patch_gen_ids(patchmask,offset,pitch,width,buffer);
      
      buffer += patch_num_tris(patchmask) * 3;
    }
  }
}
